package org.aristotle.generator.service.impl;

import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import org.aristotle.generator.config.AriTemplatesConfig;
import org.aristotle.generator.model.entity.Entity;
import org.aristotle.generator.model.project.Project;
import org.aristotle.generator.model.property.AriProperty;
import org.aristotle.generator.model.verify.VerifyRule;
import org.aristotle.generator.model.vo.AriPropertyVO;
import org.aristotle.generator.model.vo.ProjectVO;
import org.aristotle.generator.service.IGeneratorService;
import org.aristotle.generator.utils.NameUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.ResourceUtils;

import java.io.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service
public class GeneratorServiceImpl implements IGeneratorService {

    @Autowired
    private AriTemplatesConfig ariTemplatesConfig;

    private Configuration cfg;

    @Override
    public String generator(ProjectVO projectVO) {
        // 规整Project
        Project project = trimProject(projectVO);

        String path = projectVO.getPath();

        HashMap<String,String> pathMap = getPathMap(project,path,projectVO.getProjectVuePattern());

        // 初始化Freemarker
        cfg = new Configuration(Configuration.VERSION_2_3_30);
        try {
            cfg.setDirectoryForTemplateLoading(ResourceUtils.getFile("classpath:templates"));
        } catch (IOException e) {
            e.printStackTrace();
        }
        cfg.setDefaultEncoding("UTF-8");

        // 生成java文件
        generatorJavaFile(project,pathMap);
        // 生成前端vue文件
        generatorVueFile(project,pathMap);

        // 生成数据库文件
        generatorDatabase(project,pathMap);
        //test(project,pathMap);
        return path;
    }

    private HashMap<String,String> getPathMap(Project project, String path,Boolean projectVuePattern) {
        // 规整各路径
        HashMap<String,String> pathMap = new HashMap();
        pathMap.put("root",path + "/" + project.getName());
        pathMap.put("root.service", pathMap.get("root") + "/" + project.getName() + "-service");
        pathMap.put("root.interface", pathMap.get("root") + "/" + project.getName() + "-interface");
        pathMap.put("root.service.java",pathMap.get("root.service") + "/src/main/java/" + project.getPackageSlashName());
        pathMap.put("root.service.resources",pathMap.get("root.service") + "/src/main/resources/");
        pathMap.put("root.interface.java",pathMap.get("root.interface") + "/src/main/java/" + project.getPackageSlashName());
        pathMap.put("root.interface.resources",pathMap.get("root.interface") + "/src/main/resources/");
        pathMap.put("root.sql",pathMap.get("root") + "/sql/");
        if (projectVuePattern) {
            pathMap.put("root.vue",pathMap.get("root") + "/vue/vue-antd-admin/src/");
        } else {
            pathMap.put("root.vue",pathMap.get("root") + "/vue/src/");
        }

        pathMap.keySet().forEach(key -> {
            File file = new File(pathMap.get(key));
            if(!file.exists()){
                file.mkdirs();
            }
        });
        return pathMap;
    }


    /**
     * 规整Project对象
     * @param projectVO
     * @return project
     */
    private Project trimProject(ProjectVO projectVO) {
        Project project = new Project();
        project.setName(projectVO.getName());
        project.setChName(projectVO.getChName());
        project.setPort(projectVO.getPort());
        project.setDbName(projectVO.getDbName());
        project.setPackageName(projectVO.getPackageName());
        project.setPackageSlashName(NameUtils.dotToSlash(project.getPackageName()));
        project.setUpperName(NameUtils.transverseLineToHump(project.getName()));
        project.setLowerName(NameUtils.lowerFirstLetter(project.getUpperName()));
        project.setUlName(NameUtils.humpToLine(project.getUpperName()));
        project.setDictList(projectVO.getDictList() == null? new ArrayList<>():projectVO.getDictList());

        // 填充字典
        project.getDictList().forEach(item -> {
            item.setName(NameUtils.upperFirstLetter(item.getName()));
            item.setUpperName(item.getName());
            item.setUlName(NameUtils.humpToLine(item.getName()));
            item.setUulName(item.getUlName().toUpperCase());
            item.setLowerName(NameUtils.lowerFirstLetter(item.getName()));
        });


        HashMap<String, Entity> map = new HashMap<>();
        // 遍历所有实体
        projectVO.getEntityVOList().forEach(item -> {
            Entity entity = new Entity();
            entity.setName(item.getName());
            entity.setChName(item.getChName());
            entity.setUlName(NameUtils.humpToLine(item.getName()));
            entity.setUulName(entity.getUlName().toUpperCase());
            entity.setLowerName(NameUtils.lowerFirstLetter(item.getName()));
            entity.setUpperName(NameUtils.upperFirstLetter(item.getName()));
            entity.setAssignKey(item.getAssignKey());
            entity.setLogicDelete(item.getLogicDelete());
            entity.setUpdateTime(item.getUpdateTime());
            entity.setCreateTime(item.getCreateTime());
            entity.setPropertyList(new ArrayList<>());
            item.getPropertyList().forEach(prop -> {
                entity.getPropertyList().add(propertyVoToPo(prop));
            });
            entity.setDictPropList(new ArrayList<>());
            entity.setKey(getKey(entity));
            entity.setForeignKeyList(new ArrayList<AriProperty>());
            entity.setSearchList(new ArrayList<>());
            entity.setTableColumnsList(new ArrayList<>());
            entity.setRelationEntityList(new ArrayList<Entity>());
            entity.setToManyEntityList(new ArrayList<Entity>());
            entity.setToOneEntityList(new ArrayList<Entity>());
            entity.setRelation(false);
            map.put(item.getName(),entity);
        });
        // 遍历所有关系
        projectVO.getRelationVOList().forEach(item -> {
            // 是多对多关系
            Entity relation = new Entity();
            relation.setName(item.getName());
            relation.setChName(item.getChName());
            relation.setUlName(NameUtils.humpToLine(item.getName()));
            relation.setUulName(relation.getUlName().toUpperCase());
            relation.setLowerName(NameUtils.lowerFirstLetter(item.getName()));
            relation.setUpperName(NameUtils.upperFirstLetter(item.getName()));
            relation.setAssignKey(item.getAssignKey());
            relation.setLogicDelete(item.getLogicDelete());
            relation.setUpdateTime(item.getUpdateTime());
            relation.setCreateTime(item.getCreateTime());
            relation.setPropertyList(new ArrayList<>());
            item.getPropertyList().forEach(prop -> {
                relation.getPropertyList().add(propertyVoToPo(prop));
            });

            relation.setForeignKeyList(new ArrayList<>());
            relation.setKey(getKey(relation));
            relation.setDictPropList(new ArrayList<>());

            relation.setSearchList(new ArrayList<>());
            relation.setTableColumnsList(new ArrayList<>());

            relation.setRelationEntityList(new ArrayList<Entity>());
            relation.setToManyEntityList(new ArrayList<>());
            relation.setToOneEntityList(new ArrayList<>());
            relation.setRelation(true);
            if (item.getManyToManyFlag()) {
                // 多对多逻辑
                // 填充其他表的relationEntityList
                item.getManyEntityNameList().forEach(name -> {
                    Entity entity = map.get(name);
                    entity.getRelationEntityList().add(relation);
                    // 添加外键字段
                    relation.getForeignKeyList().add(getForeignKey(entity));
                    relation.getToOneEntityList().add(entity);
                });
                // 填充本表的外键
                map.put(relation.getName(),relation);
            }
            // 是一对多关系
            if (!item.getManyToManyFlag()) {
                // 一对多逻辑
                Entity one = map.get(item.getOneEntityName());
                Entity many = map.get(item.getManyEntityName());
                // 在many里添加一个外键（one的主键）
                many.getForeignKeyList().add(getForeignKey(one));
                one.getToManyEntityList().add(many);
                many.getToOneEntityList().add(one);
            }
        });
        // 遍历所有实体
        List<Entity> entityList = new ArrayList<>();
        map.keySet().forEach(key -> {
            Entity entity = map.get(key);

            entity.setRepresent(entity.getKey());

            entity.getPropertyList().forEach(prop -> {
                prop.setUlName(NameUtils.humpToLine(prop.getName()));
                prop.setLowerName(NameUtils.lowerFirstLetter(prop.getName()));
                prop.setUpperName(NameUtils.upperFirstLetter(prop.getName()));

                // 填充represent字段
                if (prop.getRepresent()) {
                    entity.setRepresent(prop);
                }
                // 填充searchList字段
                if (prop.getSearch()) {
                    entity.getSearchList().add(prop);
                }
                // 填充TableColumnsList字段
                if (prop.getShowTable()) {
                    entity.getTableColumnsList().add(prop);
                }

                if (prop.getDict()) {
                    entity.getDictPropList().add(prop);
                }
            });

            entityList.add(entity);
        });
        project.setEntityList(entityList);
        return project;
    }

    private AriProperty propertyVoToPo(AriPropertyVO prop) {
        AriProperty ap = new AriProperty();
        ap.setName(prop.getName());
        ap.setChName(prop.getChName());
        ap.setUlName(NameUtils.humpToLine(prop.getName()));
        ap.setLowerName(NameUtils.lowerFirstLetter(prop.getName()));
        ap.setUpperName(NameUtils.upperFirstLetter(prop.getName()));
        ap.setType(prop.getType());
        ap.setDict(prop.getDict());
        ap.setDictName(prop.getDictName());
        ap.setShowTable(prop.getShowTable());
        ap.setRepresent(prop.getRepresent());
        ap.setBigText(prop.getBigText());
        ap.setSearch(prop.getSearch());
        ap.setLike(prop.getLike());
        ap.setVerify(prop.getVerify());
        ap.setNotNull(prop.getNotNull());
        if (prop.getVerify()) {
            List<VerifyRule> rules = new ArrayList<>();
            if (prop.getNotNull()) {
                VerifyRule v = new VerifyRule();
                v.setType("NOT-NULL");
                v.setHelp(prop.getChName() + "不为空");
                rules.add(v);
            }
            if (prop.getPureLetter()) {
                VerifyRule v = new VerifyRule();
                v.setType("PURE-LETTER");
                v.setHelp(prop.getChName() + "只能包含字母");
                rules.add(v);
            }
            if (prop.getNumberLetter()) {
                VerifyRule v = new VerifyRule();
                v.setType("NUMBER-LETTER");
                v.setHelp(prop.getChName() + "只能包含字母和数字");
                rules.add(v);
            }
            if (prop.getLengthLimit()) {
                VerifyRule v = new VerifyRule();
                v.setType("LENGTH-LIMIT");
                Integer min = prop.getMinLength();
                Integer max = prop.getMaxLength();
                Integer swap;
                if (min > max) {
                    swap = min;
                    min = max;
                    max = swap;
                }
                v.setMinLength(min);
                v.setMaxLength(max);
                if (min == 0&&max > 0) {
                    v.setHelp(prop.getChName() + "长度最长为" + max + "位");
                } else if (max == 0&&min > 0) {
                    v.setHelp(prop.getChName() + "长度最少为" + min + "位");
                } else {
                    v.setHelp(prop.getChName() + "长度为" + min + "~" + max + "位");
                }
                rules.add(v);
            }
            ap.setRules(rules);
        }
        return ap;
    }

    private AriProperty getKey(Entity entity) {
        AriProperty prop = new AriProperty();
        prop.setName("Id");
        prop.setChName(entity.getChName() + "Id");
        prop.setUlName(NameUtils.humpToLine(prop.getName()));
        prop.setType("Long");
        prop.setLowerName(NameUtils.lowerFirstLetter(prop.getName()));
        prop.setUpperName(NameUtils.upperFirstLetter(prop.getName()));
        prop.setShowTable(true);
        prop.setSearch(true);
        return prop;
    }

    private AriProperty getForeignKey(Entity entity) {
        AriProperty prop = new AriProperty();
        prop.setName(entity.getName() + "Id");
        prop.setChName(entity.getChName() + "Id");
        prop.setUlName(NameUtils.humpToLine(prop.getName()));
        prop.setType("Long");
        prop.setLowerName(NameUtils.lowerFirstLetter(prop.getName()));
        prop.setUpperName(NameUtils.upperFirstLetter(prop.getName()));
        prop.setShowTable(true);
        prop.setSearch(true);
        return prop;
    }

    private boolean test(Project project,HashMap<String,String> pathMap) {
        // 上下文
        Map<String,Object> data = new HashMap<>();
        data.put("packageName",project.getPackageName());
        data.put("Project",project);

        project.getEntityList().forEach(entity -> {
            if (entity.getRelation()) {
                return;
            }
            // 生成多对一的drawer
            entity.getToOneEntityList().forEach(item -> {
                data.put("FEntity",entity);
                data.put("Entity",item);
                OutputStream fos = null;
                try {
                    // 获取模板
                    Template temp = cfg.getTemplate("/vue/pages/entity/drawer/one/Drawer.atl");
                    // 获取文件名
                    String name = "Drawer";
                    // 获取后缀
                    String suffix = "vue";
                    // 获取生成文件路径
                    String path = pathMap.get("root") + "/vue/pages/"+ entity.getLowerName() +"/drawer/" + item.getLowerName() + "/";
                    System.out.println(path + name + "." + suffix + "生成中...");
                    // 生成文件夹
                    File file = new File(path);
                    if(!file.exists()){
                        file.mkdirs();
                    }
                    // 创建输出流
                    fos = new FileOutputStream(new File(file,name + "." + suffix));
                    Writer writer = new OutputStreamWriter(fos);
                    temp.process(data,writer);
                    System.out.println(path + name + "." + suffix + "生成完成");
                } catch (IOException | TemplateException e) {
                    e.printStackTrace();
                } finally {
                    try {
                        fos.flush();
                        fos.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }


            });

            // 生成多对多的drawer
            entity.getRelationEntityList().forEach(relation->{
                data.put("FEntity",relation);
                StringBuffer sb = new StringBuffer();
                // 拼接文件夹名
                sb.append(relation.getLowerName());

                relation.getToOneEntityList().forEach(item -> {
                    if (!item.getName().equals(entity.getName())) {
                        data.put("Entity",item);
                        sb.append(item.getUpperName());
                    }
                });
                String folderName = sb.toString();


                OutputStream fos = null;
                try {
                    // 获取模板
                    Template temp = cfg.getTemplate("/vue/pages/entity/drawer/many/Drawer.atl");
                    // 获取文件名
                    String name = "Drawer";
                    // 获取后缀
                    String suffix = "vue";
                    // 获取生成文件路径
                    String path = pathMap.get("root") + "/vue/pages/"+ entity.getLowerName() +"/drawer/" + folderName + "/";
                    System.out.println(path + name + "." + suffix + "生成中...");
                    // 生成文件夹
                    File file = new File(path);
                    if(!file.exists()){
                        file.mkdirs();
                    }
                    // 创建输出流
                    fos = new FileOutputStream(new File(file,name + "." + suffix));
                    Writer writer = new OutputStreamWriter(fos);
                    temp.process(data,writer);
                    System.out.println(path + name + "." + suffix + "生成完成");
                } catch (IOException | TemplateException e) {
                    e.printStackTrace();
                } finally {
                    try {
                        fos.flush();
                        fos.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }

            });
        });
        return true;
    }

    private Boolean generatorJavaFile(Project project, HashMap<String,String> pathMap) {
        generatorProjectFile(project,pathMap);
        generatorEntityFile(project,pathMap);
        return true;
    }

    /**
     * 生成后端项目固定文件
     * @param project
     * @param pathMap
     * @return 是否成功
     */
    private Boolean generatorProjectFile(Project project, HashMap<String,String> pathMap) {
        List<AriTemplatesConfig.TemplatePathItem> generalList = ariTemplatesConfig.getPathConfig().getServerCode().getGeneralCode();

        // 上下文
        Map<String,Object> data = new HashMap<>();
        data.put("packageName",project.getPackageName());
        data.put("Project",project);


        generalList.forEach(templatePathItem -> {

            String name = templatePathItem.getName()
                    .replaceAll("-u-", project.getUpperName())
                    .replaceAll("-l-",project.getLowerName());
            String path = pathMap.get(templatePathItem.getRoot()) + "/" + templatePathItem.getPath() + "/";
            generatorByTemplate(templatePathItem.getTemplate(),name,templatePathItem.getSuffix(),path,data);
        });
        return true;
    }

    /**
     * 生成后端各个实体对应文件
     * @param project
     * @param pathMap
     * @return
     */
    private Boolean generatorEntityFile(Project project, HashMap<String,String> pathMap) {
        List<AriTemplatesConfig.TemplatePathItem> modelCode = ariTemplatesConfig.getPathConfig().getServerCode().getModelCode();

        // 上下文
        Map<String,Object> data = new HashMap<>();
        data.put("packageName",project.getPackageName());

        project.getEntityList().forEach(entity -> {
            data.put("Entity",entity);
            modelCode.forEach(templatePathItem -> {
                String name = templatePathItem.getName()
                        .replaceAll("-u-", entity.getUpperName())
                        .replaceAll("-l-",entity.getLowerName());
                // 获取生成文件路径
                String path = pathMap.get(templatePathItem.getRoot()) + "/" + templatePathItem.getPath() + "/";
                generatorByTemplate(templatePathItem.getTemplate(),name,templatePathItem.getSuffix(),path,data);
            });
        });
        return true;
    }

    private Boolean generatorVueFile(Project project, HashMap<String,String> pathMap) {
        // 遍历general-code
        generatorVueByGeneral(project,pathMap);
        // 遍历dict-code
        generatorVueByDict(project,pathMap);
        // 遍历model-code
        generatorVueByModel(project,pathMap);
        return true;
    }

    private Boolean generatorVueByGeneral(Project project, HashMap<String,String> pathMap) {
        List<AriTemplatesConfig.TemplatePathItem> generalCode = ariTemplatesConfig.getPathConfig().getClientCode().getGeneralCode();
        // 上下文
        Map<String,Object> data = new HashMap<>();
        data.put("Project",project);
        generalCode.forEach(templatePathItem -> {
            String path = pathMap.get(templatePathItem.getRoot()) + "/" + templatePathItem.getPath() + "/";
            generatorByTemplate(
                    templatePathItem.getTemplate(),
                    templatePathItem.getName(),
                    templatePathItem.getSuffix(),
                    path,
                    data
            );
        });

        return true;
    }

    private Boolean generatorVueByDict(Project project, HashMap<String,String> pathMap) {
        List<AriTemplatesConfig.TemplatePathItem> dictCode = ariTemplatesConfig.getPathConfig().getClientCode().getDictCode();
        // 上下文
        Map<String,Object> data = new HashMap<>();
        data.put("Project",project);
        project.getDictList().forEach(dict -> {
            data.put("Dict",dict);
            dictCode.forEach(templatePathItem -> {
                String path = pathMap.get(templatePathItem.getRoot()) + "/" + templatePathItem.getPath() + "/";
                String name = templatePathItem.getName().replaceAll("-upperDict-", dict.getUpperName());
                generatorByTemplate(
                        templatePathItem.getTemplate(),
                        name,
                        templatePathItem.getSuffix(),
                        path,
                        data
                );
            });
        });
        return true;
    }

    private Boolean generatorVueByModel(Project project, HashMap<String,String> pathMap) {
        List<AriTemplatesConfig.TemplatePathItem> modelCode = ariTemplatesConfig.getPathConfig().getClientCode().getModelCode();
        // 上下文
        Map<String,Object> data = new HashMap<>();
        data.put("Project",project);
        project.getEntityList().forEach(entity -> {
            data.put("Entity",entity);
            modelCode.forEach(templatePathItem -> {
                if ("notRelation".equals(templatePathItem.getType()) && entity.getRelation()) {
                    return;
                }
                String path = pathMap.get(templatePathItem.getRoot()) + "/" + templatePathItem.getPath() + "/";
                path = path.replaceAll("-lowerEntity-", entity.getLowerName());
                String name = templatePathItem.getName().replaceAll("-lowerEntity-", entity.getLowerName());
                generatorByTemplate(
                        templatePathItem.getTemplate(),
                        name,
                        templatePathItem.getSuffix(),
                        path,
                        data
                );
            });
            generatorVueByToOne(entity,pathMap);
            generatorVueByToRelation(entity,pathMap);
        });


        return true;
    }

    private Boolean generatorVueByToOne(Entity entity, HashMap<String,String> pathMap) {
        if (entity.getRelation()) {
            return false;
        }
        List<AriTemplatesConfig.TemplatePathItem> toOneCode = ariTemplatesConfig.getPathConfig().getClientCode().getToOneCode();
        // 上下文
        Map<String,Object> data = new HashMap<>();
        data.put("FEntity",entity);
        entity.getToOneEntityList().forEach(entityItem -> {
            data.put("Entity",entityItem);
            toOneCode.forEach(templatePathItem -> {
                String path = pathMap.get(templatePathItem.getRoot()) + "/" + templatePathItem.getPath() + "/";
                path = path.replaceAll("-lowerEntity-", entity.getLowerName()).replaceAll("-lowerOneEntity-",entityItem.getLowerName());
                String name = templatePathItem.getName().replaceAll("-lowerEntity-", entity.getLowerName()).replaceAll("-lowerOneEntity-",entityItem.getLowerName());
                generatorByTemplate(
                        templatePathItem.getTemplate(),
                        name,
                        templatePathItem.getSuffix(),
                        path,
                        data
                );
            });
        });
        return true;
    }

    private Boolean generatorVueByToRelation(Entity entity, HashMap<String,String> pathMap) {
        if (entity.getRelation()) {
            return false;
        }
        List<AriTemplatesConfig.TemplatePathItem> toRelationCode = ariTemplatesConfig.getPathConfig().getClientCode().getToRelationCode();
        // 上下文
        Map<String,Object> data = new HashMap<>();
        // 生成多对多的drawer
        entity.getRelationEntityList().forEach(relation->{
            data.put("FEntity",relation);
            StringBuffer sb = new StringBuffer();
            // 拼接文件夹名
            sb.append(relation.getLowerName());
            relation.getToOneEntityList().forEach(item -> {
                if (!item.getName().equals(entity.getName())) {
                    data.put("Entity",item);
                    sb.append(item.getUpperName());
                }
            });
            String folderName = sb.toString();
            toRelationCode.forEach(templatePathItem -> {
                String path = pathMap.get(templatePathItem.getRoot()) + "/" + templatePathItem.getPath() + "/";
                path = path.replaceAll("-lowerEntity-", entity.getLowerName()).replaceAll("-lowerRelataionToEntity-",folderName);
                String name = templatePathItem.getName().replaceAll("-lowerEntity-", entity.getLowerName()).replaceAll("-lowerRelataionToEntity-",folderName);
                generatorByTemplate(
                        templatePathItem.getTemplate(),
                        name,
                        templatePathItem.getSuffix(),
                        path,
                        data
                );
            });
        });
        return true;
    }

    private Boolean generatorDatabase(Project project,HashMap<String,String> pathMap) {
        Map<String,Object> data = new HashMap<>();
        data.put("Project",project);
        String path = pathMap.get("root.sql");
        generatorByTemplate("/sql/db.atl",project.getDbName(),"sql",path,data);
        return true;
    }


    /**
     * 根据模板生成文件
     * @param tempPath /模板位置/模板名
     * @param fileName 生成文件名
     * @param suffix 后缀
     * @param path 生成路径
     * @param data Freemarker上下文
     * @return
     */
    private Boolean generatorByTemplate(String tempPath,String fileName,String suffix,String path,Map<String,Object> data) {
        OutputStream fos = null;
        try {
            // 获取模板
            Template temp = cfg.getTemplate(tempPath);
            System.out.println(path + fileName + "." + suffix + "生成中...");
            // 生成文件夹
            File file = new File(path);
            if(!file.exists()){
                file.mkdirs();
            }
            // 创建输出流
            fos = new FileOutputStream(new File(file,fileName + "." + suffix));
            Writer writer = new OutputStreamWriter(fos);
            temp.process(data,writer);
            System.out.println(path + fileName + "." + suffix + "生成完成");
        } catch (IOException | TemplateException e) {
            e.printStackTrace();
        } finally {
            try {
                fos.flush();
                fos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return true;
    }
}
